package websocket

import (
	"context"
	"github.com/gogf/gf/v2/errors/gcode"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/os/gtime"
	"github.com/gorilla/websocket"
	"github.com/mssola/useragent"
	"runtime/debug"
)

//const (
//设置消息写入writer对象的时长，超出后报错：w.Close() write tcp 127.0.0.1:8081->127.0.0.1:60355: i/o timeout
// Time allowed to write a message to the peer.
//writeWait = 10 * time.Second

/***
  * 为了统一消息发送的格式，所以自行维护客户端心跳逻辑，不用 pongWait & pingPeriod 两个参数
  * pongWait 和 pingPeriod 两个参数主要是用于从服务端主动发起ping消息到接收客户端pong消息的流程控制，
  * 若超时视为断开连接【若选择客户端主动发起ping消息则不需要设置这两个参数，自行添加逻辑维护客户端保持心跳】
  * pongWait 参数的作用是设置服务端等待客户端响应 pong 消息的时间。如果客户端在超过该时间后仍然没有发送 pong 消息，服务端可能会认为客户端已经断开连接，并采取相应的处理措施。
  * 常用方法：1->:服务端主动发起ping消息；2->客户端接收到消息后返回pong消息（此时服务端需要通过c.conn.SetPongHandler函数对ping进行处理）
  * _ = c.conn.SetReadDeadline(time.Now().Add(pongWait))，
  //
  // Time allowed to read the next pong message from the peer.
  //pongWait = 30 * time.Second

  // 服务端会定期向客户端发送 ping 消息，以维持连接的活跃状态。 客户端在收到 ping 消息后，需要及时发送 pong 消息作为响应。而 pongWait 参数指定了服务端等待客户端响应 pong 消息的时间，
  // 即客户端在收到 ping 消息后需要在一定时间内发送 pong 消息，否则服务端可能会认为客户端已断开连接。
  // pingPeriod 参数的设置应该小于 pongWait 参数，以确保客户端有足够的时间来响应 ping 消息。这样可以确保服务端能够及时检测到客户端的连接状态，并维持连接的活跃性。
  // Send pings to peer with this period. Must be less than pongWait.
  //pingPeriod = (pongWait * 9) / 10
  ***
*/

// 超过最大长度将会自动断开连接，改读配置文件
// Maximum message size allowed from peer.
// maxMessageSize = 5120
//)

type Client struct {
	id               int64            //用户唯一ID
	conn             *websocket.Conn  //用户连接
	send             chan *WsResponse //待发送给客户端的数据
	sendClosed       bool             //是否关闭发送通道
	connTime         *gtime.Time      //用户首次连接的时间
	heartbeatTimeout *gtime.Time      //心跳超时时间：给客户端发送消息时自动更新该字段
	user             *UserData        //用户信息
	closeSignal      chan struct{}    //客户端关闭信号
	ctx              context.Context
	userAgent        *useragent.UserAgent
}

func NewClient(r *ghttp.Request, conn *websocket.Conn, user *UserData) (client *Client) {
	ctx := r.Request.Context()
	c := &Client{
		id:          user.Id,
		conn:        conn,
		send:        make(chan *WsResponse, 100),
		sendClosed:  false,
		connTime:    gtime.Now(),
		user:        user,
		closeSignal: make(chan struct{}, 1),
		ctx:         ctx,
		userAgent:   useragent.New(r.Request.UserAgent()),
	}
	c.Heartbeat()
	return c
}

// readPump pumps messages from the websocket connection to the hub.
//
// The application runs readPump in a per-connection goroutine. The application
// ensures that there is at most one reader on a connection by executing all
// reads from this goroutine.
// 监听客户端发送给服务端的消息
func (c *Client) readPump() {
	defer func() {
		_ = c.conn.Close()
	}()
	c.conn.SetReadLimit(getReadMessageMaxSize()) //单位：容量单位：1字节(B)->1024KB->MB->GB->TB
	//保存心跳由客户端主动向服务端发起ping消息，服务端维护心跳时间并定时清理，因此无需通过SetReadDeadline&SetReadDeadline两个方法来控制心跳
	//_ = c.conn.SetReadDeadline(time.Now().Add(pongWait))
	//c.conn.SetPongHandler(func(string) error {
	//	_ = c.conn.SetReadDeadline(time.Now().Add(pongWait))
	//	Logger().Errorf(ctx, "websocket.PongHandler timeout")
	//	return nil
	//})
	for {
		_, message, err := c.conn.ReadMessage()
		if err != nil {
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
				Logger().Errorf(c.ctx, "websocket.readPump.IsUnexpectedCloseError：%s", err.Error())
			}
			break
		}

		// 调用消息处理器
		EventHandleExec(c, message)
	}
}

// writePump pumps messages from the hub to the websocket connection.
//
// A goroutine running writePump is started for each connection. The
// application ensures that there is at most one writer to a connection by
// executing all writes from this goroutine.
// 监听需要给客户端发送的消息
func (c *Client) writePump() {
	defer func() {
		_ = c.conn.Close()
	}()
	for {
		select {
		case message, ok := <-c.send:
			// 发送消息给客户端
			if !ok {
				// The hub closed the channel.
				if err := c.conn.WriteMessage(websocket.CloseMessage, []byte{}); err != nil {
					Logger().Errorf(c.ctx, "websocket conn is closed")
				}
				return
			}
			if err := c.conn.WriteJSON(message); err != nil {
				Logger().Errorf(c.ctx, "WriteJSON error：%s，data=[%v]", err.Error(), message)
				return
			}
		case <-c.closeSignal:
			// 关闭客户端
			Logger().Warningf(ctx, "client closed.clientID=[%v]userINFO[%d,%s]", c.id, c.user.Id, c.user.Username)
			return
		}
	}
}

func (c *Client) close() {
	if c.sendClosed {
		return
	}
	c.sendClosed = true
	c.closeSignal <- struct{}{}
}

// Heartbeat 设置客户端心跳超时时间
func (c *Client) Heartbeat() {
	c.heartbeatTimeout = gtime.Now().Add(getHeartbeatTimoutSecond(ctx))
}

func (c *Client) SendMessage(message *WsResponse) {
	if c == nil || c.sendClosed {
		return
	}
	defer func() {
		if r := recover(); r != nil {
			Logger().Infof(c.ctx, "SendMsg err:%+v, stack:%+v", r, string(debug.Stack()))
		}
	}()
	c.send <- message
}

func SendSuccess(c *Client, event string, data ...interface{}) {
	d := interface{}(nil)
	if len(data) > 0 {
		d = data[0]
	}
	c.SendMessage(&WsResponse{
		Code:    gcode.CodeOK.Code(),
		Event:   event,
		Message: "success",
		Data:    d,
	})
	c.Heartbeat()
}

// CloseClient 关闭指定客户端
func CloseClient(c *Client) {
	c.close()
}
